package redis

import (
	"crypto/sha1"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"strings"
	"sync"
	"time"
)

var defaultPool = NewPool()

type Pool struct {
	clients map[string]*Client
	lock    *sync.RWMutex
}

func NewPool() *Pool {
	return &Pool{
		clients: make(map[string]*Client),
		lock:    new(sync.RWMutex),
	}
}

func (this *Pool) Get(addr string) *Client {
	this.lock.RLock()
	if c, ok := this.clients[addr]; ok {
		this.lock.RUnlock()
		return c
	}
	this.lock.RUnlock()
	this.lock.Lock()
	c := NewClient(addr, "foobared")
	this.clients[addr] = c
	this.lock.Unlock()
	return c
}

func Get(addr string) *Client {
	return defaultPool.Get(addr)
}

type Client struct {
	pool *redis.Pool
	lock *sync.Mutex
}

var ClientPoolMaxActive = 10

func NewClient(addr, pwd string) *Client {
	return &Client{
		pool: &redis.Pool{
			Dial: func() (redis.Conn, error) {
				c, err := redis.Dial("tcp", addr)
				if err != nil {
					return nil, err
				}
				if pwd != "" {
					if _, err := c.Do("AUTH", pwd); err != nil {
						c.Close()
						return nil, err
					}
				}
				if _, err := c.Do("SELECT", 0); err != nil {
					c.Close()
					return nil, err
				}
				return c, nil
			},
			MaxIdle:     3,
			IdleTimeout: time.Second * 30,
			MaxActive:   ClientPoolMaxActive,
			Wait:        true,
		},
		lock: &sync.Mutex{},
	}
}

func (r *Client) Exec(cmdName string, args ...any) *Result {
	c := r.pool.Get()
	defer c.Close()
	reply, err := c.Do(cmdName, args...)
	return &Result{reply, err}
}

func (r *Client) ExecCmd(cmd *Command) *Result {
	c := r.pool.Get()
	defer c.Close()
	reply, err := c.Do(cmd.name, cmd.args...)
	return &Result{reply, err}
}

func (r *Client) Multi(cmds ...*Command) ([]*Result, error) {
	c := r.pool.Get()
	defer c.Close()

	c.Send("MULTI")
	for _, cmd := range cmds {
		if err := c.Send(cmd.name, cmd.args...); err != nil {
			return nil, err
		}
	}
	reply, err := c.Do("EXEC")
	if err != nil {
		return nil, err
	}
	rows := []*Result{}
	for _, r := range reply.([]any) {
		res := &Result{nil, nil}
		if e, ok := r.(error); ok {
			res.err = e
		} else {
			res.data = r
		}
		rows = append(rows, res)
	}
	return rows, nil
}

func (r *Client) Scan(fn func(string) bool) error {
	c := r.pool.Get()
	defer c.Close()

	cur := 0
	for {
		vs, err := redis.Values(c.Do("SCAN", cur))
		if err != nil {
			return err
		}
		cur, _ = redis.Int(vs[0], nil)
		keys, _ := redis.Strings(vs[1], nil)
		for _, key := range keys {
			if !fn(key) {
				return nil
			}
		}
		if cur == 0 {
			break
		}
	}
	return nil
}

func (r *Client) Keys() ([]string, error) {
	keys := []string{}
	if err := r.Scan(func(key string) bool {
		keys = append(keys, key)
		return true
	}); err != nil {
		return nil, err
	}
	return keys, nil
}

type Script struct {
	hash   string
	script string
}

func NewScript(script string) *Script {
	hash := getSHA1(script)
	return &Script{hash: hash, script: script}
}

func (r *Client) ExecScript(script *Script, keys []string, args ...any) *Result {
	c := r.pool.Get()
	defer c.Close()

	keyCnt := 0
	if keys != nil {
		keyCnt = len(keys)
	}

	ss := []any{keyCnt}
	for _, key := range keys {
		ss = append(ss, key)
	}
	ss = append(ss, args...)

	reply, err := c.Do("EVALSHA", append([]any{script.hash}, ss...)...)
	if e, ok := err.(redis.Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
		reply, err = c.Do("EVAL", append([]any{script.script}, ss...)...)
	}
	return &Result{reply, err}
}

func getSHA1(s string) string {
	h := sha1.New()
	h.Write([]byte(s))
	return fmt.Sprintf("%x", h.Sum(nil))
}

type Result struct {
	data any
	err  error
}

var ErrNil = redis.ErrNil

func (r *Result) Error() error {
	return r.err
}

func (r *Result) Bool() (bool, error) {
	switch data := r.data.(type) {
	case string:
		if data == "OK" {
			return true, nil
		}
	}
	return redis.Bool(r.data, r.err)
}

func (r *Result) ByteSlices() ([][]byte, error) {
	return redis.ByteSlices(r.data, r.err)
}

func (r *Result) Bytes() ([]byte, error) {
	return redis.Bytes(r.data, r.err)
}

func (r *Result) Float64() (float64, error) {
	return redis.Float64(r.data, r.err)
}

func (r *Result) Int() (int, error) {
	return redis.Int(r.data, r.err)
}

func (r *Result) IntMap() (map[string]int, error) {
	return redis.IntMap(r.data, r.err)
}

func (r *Result) Int64() (int64, error) {
	return redis.Int64(r.data, r.err)
}

func (r *Result) Int64Map() (map[string]int64, error) {
	return redis.Int64Map(r.data, r.err)
}

func (r *Result) Ints() ([]int, error) {
	return redis.Ints(r.data, r.err)
}

func (r *Result) String() (string, error) {
	return redis.String(r.data, r.err)
}

func (r *Result) Strings() ([]string, error) {
	return redis.Strings(r.data, r.err)
}

func (r *Result) StringMap() (map[string]string, error) {
	return redis.StringMap(r.data, r.err)
}

func (r *Result) Values() ([]any, error) {
	return redis.Values(r.data, r.err)
}

type Command struct {
	name string
	args []any
}

type CmdGen struct{}

var Cmd = &CmdGen{}

func (r *CmdGen) Cmd(name string, args ...any) *Command {
	return &Command{name, args}
}

// Key

func (r *CmdGen) Del(keys ...string) *Command {
	args := []any{}
	for _, key := range keys {
		args = append(args, key)
	}
	return &Command{"DEL", args}
}

func (r *CmdGen) Dump(key string) *Command {
	return &Command{"DUMP", []any{key}}
}

func (r *CmdGen) Exists(key string) *Command {
	return &Command{"EXISTS", []any{key}}
}

func (r *CmdGen) Expire(key string, seconds int) *Command {
	return &Command{"EXPIRE", []any{key, seconds}}
}

func (r *CmdGen) ExpireAt(key string, timestamp int64) *Command {
	return &Command{"EXPIREAT", []any{key, timestamp}}
}

func (r *CmdGen) Keys(pattern string) *Command {
	return &Command{"KEYS", []any{pattern}}
}

func (r *CmdGen) Move(key string, db int) *Command {
	return &Command{"MOVE", []any{key, db}}
}

func (r *CmdGen) Persist(key string) *Command {
	return &Command{"PERSIST", []any{key}}
}

func (r *CmdGen) PExpire(key string, milliseconds int) *Command {
	return &Command{"PEXPIRE", []any{key, milliseconds}}
}

func (r *CmdGen) PExpireAt(key string, ms_timestamp int64) *Command {
	return &Command{"PEXPIREAT", []any{key, ms_timestamp}}
}

func (r *CmdGen) PTTL(key string) *Command {
	return &Command{"PTTL", []any{key}}
}

func (r *CmdGen) RandomKey() *Command {
	return &Command{"RANDOMKEY", []any{}}
}

func (r *CmdGen) Rename(old, new string) *Command {
	return &Command{"RENAME", []any{old, new}}
}

func (r *CmdGen) RenameNx(old, new string) *Command {
	return &Command{"RENAMENX", []any{old, new}}
}

func (r *CmdGen) Restore(key string, ms_ttl int64, data string, replace bool) *Command {
	args := []any{key, ms_ttl, data}
	if replace {
		args = append(args, "REPLACE")
	}
	return &Command{"RESTORE", args}
}

func (r *CmdGen) TTL(key string) *Command {
	return &Command{"TTL", []any{key}}
}

func (r *CmdGen) Type(key string) *Command {
	return &Command{"TYPE", []any{key}}
}

// String

func (r *CmdGen) Append(key, value string) *Command {
	return &Command{"APPEND", []any{key, value}}
}

func (r *CmdGen) BitCount(key string, start, end int) *Command {
	return &Command{"BITCOUNT", []any{key, start, end}}
}

func (r *CmdGen) Decr(key string) *Command {
	return &Command{"DECR", []any{key}}
}

func (r *CmdGen) DecrBy(key string, value int) *Command {
	return &Command{"DECRBY", []any{key, value}}
}

func (r *CmdGen) Get(key string) *Command {
	return &Command{"GET", []any{key}}
}

func (r *CmdGen) GetBit(key string, offset int) *Command {
	return &Command{"GETBIT", []any{key, offset}}
}

func (r *CmdGen) GetRange(key string, start, end int) *Command {
	return &Command{"GETRANGE", []any{key, start, end}}
}

func (r *CmdGen) GetSet(key, value string) *Command {
	return &Command{"GETSET", []any{key, value}}
}

func (r *CmdGen) Incr(key string) *Command {
	return &Command{"INCR", []any{key}}
}

func (r *CmdGen) IncrBy(key string, value int64) *Command {
	return &Command{"INCRBY", []any{key, value}}
}

func (r *CmdGen) IncrByFloat(key string, value float64) *Command {
	return &Command{"INCRBYFLOAT", []any{key, value}}
}

func (r *CmdGen) MGet(keys ...string) *Command {
	args := []any{}
	for _, key := range keys {
		args = append(args, key)
	}
	return &Command{"MGET", args}
}

func (r *CmdGen) MSet(kvs map[string]string) *Command {
	args := []any{}
	for k, v := range kvs {
		args = append(args, k, v)
	}
	return &Command{"MSET", args}
}

func (r *CmdGen) MSetNx(kvs map[string]string) *Command {
	args := []any{}
	for k, v := range kvs {
		args = append(args, k, v)
	}
	return &Command{"MSETNX", args}
}

func (r *CmdGen) PSetEx(key, value string, milliseconds int64) *Command {
	return &Command{"PSETEX", []any{key, milliseconds, value}}
}

func (r *CmdGen) Set(key string, value any) *Command {
	return &Command{"SET", []any{key, value}}
}

func (r *CmdGen) SetBit(key string, offset int, value int) *Command {
	if value != 0 {
		value = 1
	}
	return &Command{"SETBIT", []any{key, offset, value}}
}

func (r *CmdGen) SetEx(key, value string, seconds int) *Command {
	return &Command{"SETEX", []any{key, seconds, value}}
}

func (r *CmdGen) SetNx(key, value string) *Command {
	return &Command{"SETNX", []any{key, value}}
}

func (r *CmdGen) SetXx(key, value string) *Command {
	return &Command{"SET", []any{key, value, "XX"}}
}

func (r *CmdGen) SetRange(key string, offset int, value string) *Command {
	return &Command{"SETRANGE", []any{key, offset, value}}
}

func (r *CmdGen) StrLen(key string) *Command {
	return &Command{"STRLEN", []any{key}}
}

// Hash

func (r *CmdGen) HDel(key string, fields ...string) *Command {
	args := []any{key}
	for _, f := range fields {
		args = append(args, f)
	}
	return &Command{"HDEL", args}
}

func (r *CmdGen) HExists(key, field string) *Command {
	return &Command{"HEXISTS", []any{key, field}}
}

func (r *CmdGen) HGet(key, field string) *Command {
	return &Command{"HGET", []any{key, field}}
}

func (r *CmdGen) HGetAll(key string) *Command {
	return &Command{"HGETALL", []any{key}}
}

func (r *CmdGen) HIncrBy(key, field string, value int64) *Command {
	return &Command{"HINCRBY", []any{key, field, value}}
}

func (r *CmdGen) HIncrByFloat(key, field string, value float64) *Command {
	return &Command{"HINCRBYFLOAT", []any{key, field, value}}
}

func (r *CmdGen) HKeys(key string) *Command {
	return &Command{"HKEYS", []any{key}}
}

func (r *CmdGen) HLen(key string) *Command {
	return &Command{"HLEN", []any{key}}
}

func (r *CmdGen) HMGet(key string, fields ...string) *Command {
	args := []any{key}
	for _, f := range fields {
		args = append(args, f)
	}
	return &Command{"HMGET", args}
}

func (r *CmdGen) HMSet(key string, kvs map[string]any) *Command {
	args := []any{key}
	for k, v := range kvs {
		args = append(args, k, v)
	}
	return &Command{"HMSET", args}
}

func (r *CmdGen) HSet(key, field string, value any) *Command {
	return &Command{"HSET", []any{key, field, value}}
}

func (r *CmdGen) HSetNx(key, field string, value any) *Command {
	return &Command{"HSETNX", []any{key, field, value}}
}

func (r *CmdGen) HVals(key string) *Command {
	return &Command{"HVALS", []any{key}}
}

func (r *CmdGen) HStrLen(key, field string) *Command {
	return &Command{"HSTRLEN", []any{key, field}}
}

// List

func (r *CmdGen) BLPop(timeout int, keys ...string) *Command {
	args := []any{}
	for _, key := range keys {
		args = append(args, key)
	}
	args = append(args, timeout)
	return &Command{"BLPOP", args}
}

func (r *CmdGen) BRPop(timeout int, keys ...string) *Command {
	args := []any{}
	for _, key := range keys {
		args = append(args, key)
	}
	args = append(args, timeout)
	return &Command{"BRPOP", args}
}

func (r *CmdGen) BRPopLPush(src, desc string, timeout int) *Command {
	return &Command{"BRPOPLPUSH", []any{src, desc, timeout}}
}

func (r *CmdGen) LIndex(key string, index int) *Command {
	return &Command{"LINDEX", []any{key, index}}
}

func (r *CmdGen) LInsert_before(key, pivot, value string) *Command {
	return &Command{"LINSERT", []any{key, "BEFORE", pivot, value}}
}

func (r *CmdGen) LInsert_after(key, pivot, value string) *Command {
	return &Command{"LINSERT", []any{key, "AFTER", pivot, value}}
}

func (r *CmdGen) LLen(key string) *Command {
	return &Command{"LLEN", []any{key}}
}

func (r *CmdGen) LPop(key string) *Command {
	return &Command{"LPOP", []any{key}}
}

func (r *CmdGen) LPush(key string, values ...string) *Command {
	args := []any{key}
	for _, v := range values {
		args = append(args, v)
	}
	return &Command{"LPUSH", args}
}

func (r *CmdGen) LPushX(key, value string) *Command {
	return &Command{"LPUSHX", []any{key, value}}
}

func (r *CmdGen) LRange(key string, start, end int) *Command {
	return &Command{"LRANGE", []any{key, start, end}}
}

func (r *CmdGen) LRem(key string, cnt int, value string) *Command {
	return &Command{"LREM", []any{key, cnt, value}}
}

func (r *CmdGen) LSet(key string, index int, value string) *Command {
	return &Command{"LSET", []any{key, index, value}}
}

func (r *CmdGen) LTrim(key string, start, end int) *Command {
	return &Command{"LTRIM", []any{key, start, end}}
}

func (r *CmdGen) RPop(key string) *Command {
	return &Command{"RPOP", []any{key}}
}

func (r *CmdGen) RPopLPush(src, desc string) *Command {
	return &Command{"RPOPLPUSH", []any{src, desc}}
}

func (r *CmdGen) RPush(key string, values ...string) *Command {
	args := []any{key}
	for _, v := range values {
		args = append(args, v)
	}
	return &Command{"RPUSH", args}
}

func (r *CmdGen) RPushX(key, value string) *Command {
	return &Command{"RPUSHX", []any{key, value}}
}

// Set

func (r *CmdGen) SAdd(key string, members ...string) *Command {
	args := []any{key}
	for _, v := range members {
		args = append(args, v)
	}
	return &Command{"SADD", args}
}

func (r *CmdGen) SCard(key string) *Command {
	return &Command{"SCARD", []any{key}}
}

func (r *CmdGen) SDiff(keys ...string) *Command {
	args := []any{}
	for _, k := range keys {
		args = append(args, k)
	}
	return &Command{"SDIFF", args}
}

func (r *CmdGen) SDiffStore(saveKey string, keys ...string) *Command {
	args := []any{saveKey}
	for _, k := range keys {
		args = append(args, k)
	}
	return &Command{"SDIFFSTORE", args}
}

func (r *CmdGen) SInter(keys ...string) *Command {
	args := []any{}
	for _, k := range keys {
		args = append(args, k)
	}
	return &Command{"SINTER", args}
}

func (r *CmdGen) SInterStore(saveKey string, keys ...string) *Command {
	args := []any{saveKey}
	for _, k := range keys {
		args = append(args, k)
	}
	return &Command{"SINTERSTORE", args}
}

func (r *CmdGen) SIsMember(key, member string) *Command {
	return &Command{"SISMEMBER", []any{key, member}}
}

func (r *CmdGen) SMembers(key string) *Command {
	return &Command{"SMEMBERS", []any{key}}
}

func (r *CmdGen) SMove(src, desc, member string) *Command {
	return &Command{"SMOVE", []any{src, desc, member}}
}

func (r *CmdGen) SPop(key string) *Command {
	return &Command{"SPOP", []any{key}}
}

func (r *CmdGen) SRandMember(key string, count int) *Command {
	return &Command{"SRANDMEMBER", []any{key, count}}
}

func (r *CmdGen) SRem(key string, members ...string) *Command {
	args := []any{key}
	for _, v := range members {
		args = append(args, v)
	}
	return &Command{"SREM", args}
}

func (r *CmdGen) SUnion(keys ...string) *Command {
	args := []any{}
	for _, k := range keys {
		args = append(args, k)
	}
	return &Command{"SUNION", args}
}

func (r *CmdGen) SUnionStore(saveKey string, keys ...string) *Command {
	args := []any{saveKey}
	for _, k := range keys {
		args = append(args, k)
	}
	return &Command{"SUNIONSTORE", args}
}

// SortedSet

func (r *CmdGen) ZAdd(key string, member string, score int64) *Command {
	return &Command{"ZADD", []any{key, score, member}}
}

func (r *CmdGen) ZAdd_m(key string, data map[string]int64) *Command {
	args := []any{key}
	for m, s := range data {
		args = append(args, s, m)
	}
	return &Command{"ZADD", args}
}

func (r *CmdGen) ZCard(key string) *Command {
	return &Command{"ZCARD", []any{key}}
}

func (r *CmdGen) ZCount(key string, min, max int64) *Command {
	return &Command{"ZCOUNT", []any{key, min, max}}
}

func (r *CmdGen) ZIncrBy(key string, member string, score int64) *Command {
	return &Command{"ZINCRBY", []any{key, score, member}}
}

func (r *CmdGen) ZRange(key string, start, end int) *Command {
	return &Command{"ZRANGE", []any{key, start, end}}
}

func (r *CmdGen) ZRange_withScores(key string, start, end int) *Command {
	return &Command{"ZRANGE", []any{key, start, end, "WITHSCORES"}}
}

func (r *CmdGen) ZRangeByScore(key string, min, max int64) *Command {
	return &Command{"ZRANGEBYSCORE", []any{key, min, max}}
}

func (r *CmdGen) ZRangeByScore_withScores(key string, min, max int64) *Command {
	return &Command{"ZRANGEBYSCORE", []any{key, min, max, "WITHSCORES"}}
}

func (r *CmdGen) ZRank(key, member string) *Command {
	return &Command{"ZRANK", []any{key, member}}
}

func (r *CmdGen) ZRem(key string, members ...string) *Command {
	args := []any{key}
	for _, m := range members {
		args = append(args, m)
	}
	return &Command{"ZREM", args}
}

func (r *CmdGen) ZRemRangeByRank(key string, start, stop int) *Command {
	return &Command{"ZREMRANGEBYRANK", []any{key, start, stop}}
}

func (r *CmdGen) ZRemRangeByScore(key string, min, max int64) *Command {
	return &Command{"ZREMRANGEBYSCORE", []any{key, min, max}}
}

func (r *CmdGen) ZRevRange(key string, start, end int) *Command {
	return &Command{"ZREVRANGE", []any{key, start, end}}
}

func (r *CmdGen) ZRevRange_withScores(key string, start, end int) *Command {
	return &Command{"ZREVRANGE", []any{key, start, end, "WITHSCORES"}}
}

func (r *CmdGen) ZRevRangeByScore(key string, min, max int64) *Command {
	return &Command{"ZREVRANGEBYSCORE", []any{key, min, max}}
}

func (r *CmdGen) ZRevRangeByScore_withScores(key string, min, max int64) *Command {
	return &Command{"ZREVRANGEBYSCORE", []any{key, min, max, "WITHSCORES"}}
}

func (r *CmdGen) ZRevRank(key, member string) *Command {
	return &Command{"ZREVRANK", []any{key, member}}
}

func (r *CmdGen) ZScore(key, member string) *Command {
	return &Command{"ZSCORE", []any{key, member}}
}

// HyperLogLog

func (r *CmdGen) PFAdd(key string, value any) *Command {
	return &Command{"PFADD", []any{key, value}}
}

func (r *CmdGen) PFCount(key string) *Command {
	return &Command{"PFCOUNT", []any{key}}
}

//

type Lock struct {
	key string
	val string
	r   *Client
}

func (r *Client) Lock(key string, timeout int) *Lock {
	c := r.pool.Get()
	defer c.Close()
	val := time.Now().String()
	ok, err := r.Exec("SET", key, val, "EX", timeout, "NX").Bool()
	if err != nil || !ok {
		return nil
	}
	return &Lock{key, val, r}
}

var rs_DelEq = NewScript(`if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end`)

func (r *Lock) Unlock() bool {
	ok, err := r.r.ExecScript(rs_DelEq, []string{r.key}, r.val).Bool()
	if err != nil {
		return false
	}
	return ok
}

//

func (r *CmdGen) Publish(channel string, message any) *Command {
	return &Command{"PUBLISH", []any{channel, message}}
}

func interfacesFromStrs(ss []string) []any {
	ret := make([]any, len(ss))
	for i, s := range ss {
		ret[i] = s
	}
	return ret
}

func (r *Client) Subscribe(handler func(ch string, data []byte), channels ...string) (unsubscribe func(channels ...string), err error) {
	c := redis.PubSubConn{r.pool.Get()}
	chs := interfacesFromStrs(channels)
	if err := c.Subscribe(chs...); err != nil {
		c.Close()
		return nil, err
	}
	incrMaxActive := r.pool.MaxActive > 0
	if incrMaxActive {
		r.lock.Lock()
		r.pool.MaxActive += 1
		r.lock.Unlock()
	}
	go func() {
		defer func() { recover() }()
		defer func() {
			c.Close()
			if incrMaxActive {
				r.lock.Lock()
				r.pool.MaxActive -= 1
				r.lock.Unlock()
			}
		}()
		for {
			switch v := c.Receive().(type) {
			case redis.Message:
				handler(v.Channel, v.Data)
			case redis.Subscription:
				if v.Kind == "unsubscribe" && v.Count == 0 {
					return
				}
			case error:
				return
			}
		}
	}()
	return func(channels ...string) {
		c.Unsubscribe(interfacesFromStrs(channels)...)
	}, nil
}

func (r *Client) Subscribe_s(handler func(ch, data string), channel ...string) (unsubscribe func(channels ...string), err error) {
	return r.Subscribe(func(ch string, data []byte) {
		handler(ch, string(data))
	}, channel...)
}

func (r *Client) PSubscribe(handler func(p, ch string, data []byte), patterns ...string) (punsubscribe func(patterns ...string), err error) {
	c := redis.PubSubConn{r.pool.Get()}
	chs := interfacesFromStrs(patterns)
	if err := c.PSubscribe(chs...); err != nil {
		c.Close()
		return nil, err
	}
	incrMaxActive := r.pool.MaxActive > 0
	if incrMaxActive {
		r.lock.Lock()
		r.pool.MaxActive += 1
		r.lock.Unlock()
	}
	go func() {
		defer func() { recover() }()
		defer func() {
			c.Close()
			if incrMaxActive {
				r.lock.Lock()
				r.pool.MaxActive -= 1
				r.lock.Unlock()
			}
		}()
		for {
			switch v := c.Receive().(type) {
			case redis.Message:
				handler(v.Pattern, v.Channel, v.Data)
			case redis.Subscription:
				// fmt.Println(v.Kind, v.Channel, v.Count)
				if v.Kind == "punsubscribe" && v.Count == 0 {
					return
				}
			case error:
				// fmt.Println("err", v)
				return
			}
		}
	}()
	return func(patterns ...string) {
		c.PUnsubscribe(interfacesFromStrs(patterns)...)
	}, nil
}

func (r *Client) PSubscribe_s(handler func(p, ch, data string), pattern ...string) (unsubscribe func(patterns ...string), err error) {
	return r.PSubscribe(func(p, ch string, data []byte) {
		handler(p, ch, string(data))
	}, pattern...)
}
